import csv
import math
import PackageClass
from PackageClass import Point, Vertices, PAD, BondingWire, Platform, Package
import tkinter as tk
from tkinter import ttk, Tk, Canvas, filedialog, messagebox, simpledialog
from PIL import ImageGrab
from Paras import Paras
from AppConfig import AppConfig
from AssemblyPlanConfig import AssemblyPlan
# 输入参数
class Config:
    # 画布边宽
    canvas_pad_x = 200.0
    canvas_pad_y = 1250.0
    # 初始缩放因子
    init_scare_rate = 0.3
    # 靠近捕获的距离条件
    near_condition_distance=5
    
    

# 读入原始数据，platform的pad所有坐标都加上platform的起点xy（相对于xy的坐标）
class AppMouseDrag:
    def __init__(self):
        self.canvas_x = 0
        self.canvas_y = 0
        self.x = 0
        self.y = 0
        self.dx = 0.0
        self.dy = 0.0
        self.distance = 0.0
        self.item = None
        

class App:
    def __init__(self):
        
        self.cfg:AppConfig = AppConfig("PackageViewer_config.json")
        self.plan:AssemblyPlan = None
        if self.cfg.check_valid():
            self.plan = AssemblyPlan(self.cfg.record()) 
            if not self.plan.check_valid() :
                messagebox.showinfo("Error", "a assembly plan json file or its content is invalid")
        else:
            messagebox.showinfo("Warning", "Please select a assembly plan json file")

        self.root = tk.Tk()
        self.root.title("Package Preview")
        
        # wxh + x + y
        self.root.geometry("1700x1300+50+50")
        
        self.mouse_pos = Point(0,0)
        self.mouse_real_pos = Point(0,0)
        self.package = None
        self.catchingwire = False
        
        self.pad_x = 0.0
        self.pad_y = 0.0
        
        # 初始化缩放因子
        self.scale_factor = Config.init_scare_rate
        self.default_font_size = 12
        self.font_size = 12
        
        # 创建左侧按钮区域
        self.button_frame = ttk.Frame(self.root)
        self.button_frame.grid(row=0, column=0, sticky=tk.NS)
        
        # 添加9个按钮
        buttons = {
            #"刷新": self.package_preview,
            #"缩小": self.zoom_out,
            #"放大": self.zoom_in,
            "生成空封装计划": self.new_plan,
            "选择封装计划": self.select_plan,
            "保存为PNG": self.save_canvas_as_png,
            "重读bondingTable": self.package_reinit_bondingtable,
            "保存bondingTable": self.save_bondingTable
        }
        for i, (text, command) in enumerate(buttons.items()):
            btn = ttk.Button(self.button_frame, text=text, command=command)
            btn.grid(row=i, column=0, padx=5, pady=5, sticky=tk.W+tk.E)
        
        # 创建状态栏框架
        status_frame = ttk.Frame(self.root)
        status_frame.grid(row=1, column=0, columnspan=2, sticky=tk.W+tk.E)
        
        # 创建只读文本窗口显示scale_factor
        self.scale_text = tk.Text(status_frame, height=1, width=15, state='disabled')
        self.scale_text.grid(in_=status_frame, row=0, column=0, padx=5, pady=5, sticky=tk.W+tk.E)
        
        self.mouse_pos_text = tk.Text(status_frame, height=1, width=15, state='disabled')
        self.mouse_pos_text.grid(in_=status_frame, row=0, column=1, padx=5, pady=5, sticky=tk.W+tk.E)
                
        # 创建右侧预览区域
        self.preview_frame = ttk.Frame(self.root)
        self.preview_frame.grid(row=0, column=1, sticky=tk.NSEW)
        
        # 配置网格权重
        self.root.grid_rowconfigure(0, weight=1)
        self.root.grid_columnconfigure(1, weight=1)
        
        # 创建画布
        self.canvas = tk.Canvas(self.preview_frame, bg='white')
        self.canvas.pack(fill=tk.BOTH, expand=True)
        
        
        # 初始化拖动相关的变量
        self.drag_data = AppMouseDrag()
        self.mean_data = AppMouseDrag()
        # 绑定事件
        self.canvas.bind("<Motion>", self.mouse_move) # 鼠标移动
        
        self.canvas.bind("<ButtonPress-1>", self.left_click)
        self.canvas.bind("<B1-Motion>", self.left_drag)
        self.canvas.bind("<ButtonRelease-1>", self.left_stop_drag)
        
        self.canvas.bind("<ButtonPress-3>", self.right_click)
        self.canvas.bind("<B3-Motion>", self.right_drag)
        self.canvas.bind("<ButtonRelease-3>", self.right_stop_drag)
        # 绑定滚轮事件
        self.canvas.bind("<MouseWheel>", self.on_mousewheel)  # Windows 和 MacOS
        self.canvas.bind("<Button-4>", self.on_mousewheel)    # Linux 滚动向上
        self.canvas.bind("<Button-5>", self.on_mousewheel)    # Linux 滚动向下
        self.canvas.focus_set()  # 确保canvas可以接收键盘事件        
        
        self.update_paras()        
        
        # 初始绘制方框
        self.package_init()
        self.package_preview()
    def new_plan(self):
        file_path = filedialog.asksaveasfilename(
        defaultextension=".json",
        filetypes=[("Assembly Plan Files", "*.json"), ("All files", "*.*")]
        )
        if file_path:              
            self.plan = AssemblyPlan.NewPlan(file_path)
            
    def select_plan(self):
        file_path = filedialog.askopenfilename(filetypes=[("Assembly Plan Files", "*.json")])
        if file_path :
            valid_file = True         
            self.plan:AssemblyPlan = None
            if self.cfg.check_valid():
                self.plan = AssemblyPlan(self.cfg.record()) 
                if not self.plan.check_valid() :
                    valid_file = False
                    messagebox.showinfo("Error", "a assembly plan json file or its content is invalid")
            else:
                valid_file = False
                messagebox.showinfo("Warning", "Please select a assembly plan json file")
                
            if valid_file :    
                self.package_init()
                self.package_preview()
                self.cfg.newrecord(file_path)   
            
    def save_bondingTable(self):
        self.package.save_bondingtable()
    def caculate_mousepos_from_realpos(self, event):
        x = (event.x - self.pad_x - Config.canvas_pad_x) / self.scale_factor 
        y = ((event.y - self.pad_y - Config.canvas_pad_y) / self.scale_factor) 
        if y != 0: y = -y
        return Point(x, y)
    
    def caculate_realpos_from_mousepos(self, event):
        x = event.x
        y = event.y
        if y != 0: y = -y        
        py = y * self.scale_factor + self.pad_y + Config.canvas_pad_y
        px = x * self.scale_factor + self.pad_x + Config.canvas_pad_x 
        return Point(px, py)
        
    def caculate_new_padding_after_new_scale_from_mousepos(self, event,scare=1):
        # 实际坐标对应画布上的绝对位置，缩放的时候绝对坐标是不变的
        # 鼠标坐标显示的是点在设计方案里面的坐标，由于要展示效果所以这个坐标和实际坐标之间有缩放和偏移的相对性
        # scale的变化 是实际坐标0 = 实际坐标1 且 鼠标坐标0 = 鼠标坐标1
        # 即 y * self.scale_factor + self.pad_y + Config.canvas_pad_y = y * self.scale_factor * scare + self.pad_y_new + Config.canvas_pad_y
        # 其中y即为鼠标坐标y
        # 化简得 self.pad_y_new = y * self.scale_factor * (1-scare) + self.pad_y
        x = event.x
        y = event.y
        if y != 0: y = -y        
        py = y * self.scale_factor * (1-scare) + self.pad_y
        px = x * self.scale_factor * (1-scare) + self.pad_x
        return Point(px, py)
    
    def mouse_move(self, event):  
        self.canvas_mouse_move(event)
        
        near_wire = self.package.hand_near_any_wire(Point(event.x, event.y),Config.near_condition_distance)
        in_pad = self.package.hand_in_any_pad(Point(event.x, event.y))
        
        if self.package.near_wire_changed or self.package.inside_pad_changed:
            self.package_preview()
            
    def canvas_mouse_move(self, event):  
        """更新鼠标位置"""  
        self.mouse_real_pos.set(event)            
        self.mouse_pos = self.caculate_mousepos_from_realpos(event)
        self.mouse_pos_text.config(state='normal')
        self.mouse_pos_text.delete('1.0', tk.END)
        self.mouse_pos_text.insert(tk.END, f"({self.mouse_pos.x:.0f},{self.mouse_pos.y:.0f})")
        self.mouse_pos_text.config(state='disabled')
        
    
    def on_mousewheel(self, event):
        """处理滚轮事件"""
        
        # 根据平台调整缩放比例
        if event.num == 4 or event.delta > 0:  # 向上滚动
            self.zoom_in()
        elif event.num == 5 or event.delta < 0:  # 向下滚动
            self.zoom_out()
            
    def left_click(self, event):
        """鼠标左键按下"""
        self.canvas_mouse_move(event)        
        
        self.catchingwire = self.package.hand_near_any_wire(Point(event.x, event.y),Config.near_condition_distance)
        
        if self.catchingwire :
            self.package.hand_catch_near_wire()
            self.package_preview()
        else :        
            # 记录起始位置
            self.drag_data.x = event.x
            self.drag_data.y = event.y        
            # 显示坐标信息
            self.show_coordinates(event)     
        
    def left_drag(self, event):
        """拖动过程中"""
        self.canvas_mouse_move(event)
        
        if self.catchingwire :
            self.package.hand_catch_wire_move(Point(event.x, event.y))
            self.package.hand_in_any_pad(Point(event.x, event.y))
            self.package_preview()
        else :
            # 计算偏移量
            delta_x = event.x - self.drag_data.x
            delta_y = event.y - self.drag_data.y
            self.pad_x += delta_x;
            self.pad_y += delta_y;

            # 更新所有项目的位置
            for item in self.canvas.find_all():
                self.canvas.move(item, delta_x, delta_y)
        
            # 更新拖动数据中的位置
            self.drag_data.x = event.x
            self.drag_data.y = event.y
            # 显示坐标信息
            self.show_coordinates(event)

    def left_stop_drag(self, event):
        """停止拖动"""
        self.canvas_mouse_move(event)
        # 拖动结束，可以在这里做任何清理工作        
        if self.catchingwire :            
            in_pad = self.package.hand_in_any_pad(Point(event.x, event.y))
            if not in_pad :
                self.package.hand_release_wire()
            else:
                self.package.hand_release_wire_on_pad()
            self.package.hand_dont_care_pads()
            self.catchingwire = False
            self.package_preview()

    def show_coordinates(self, event):
        # 删除之前的坐标信息
        self.canvas.delete("meansure")
        self.canvas.delete("coordinates")
        # 显示坐标信息
        pos = self.caculate_mousepos_from_realpos(event)
        coords = f"({pos.x:.0f},{pos.y:.0f})"
        radius = 1
        self.canvas.create_oval(event.x - radius, event.y - radius, event.x + radius, event.y + radius, fill='darkviolet', outline='darkviolet', tags="coordinates")
        self.canvas.create_text(event.x+50, event.y+10, text=coords, font=('Helvetica', 12, 'bold'), fill='darkviolet', tags="coordinates")
        
    def right_click(self, event):
        """开始测量"""
        self.canvas_mouse_move(event)
        # 记录起始位置
        pos = self.caculate_mousepos_from_realpos(event)
        self.mean_data.canvas_x = event.x
        self.mean_data.canvas_y = event.y
        self.mean_data.x = pos.x
        self.mean_data.y = pos.y
        # 显示坐标信息
        self.show_meansure(event)
        
    def right_drag(self, event):
        self.canvas_mouse_move(event)
        # 更新拖动数据中的位置
        pos = self.caculate_mousepos_from_realpos(event)
        self.mean_data.dx = (pos.x - self.mean_data.x)
        self.mean_data.dy = (pos.y - self.mean_data.y)
        self.show_meansure(event)     

    def right_stop_drag(self, event):
        """停止测量"""
        self.canvas_mouse_move(event)
        # 更新拖动数据中的位置
        pos = self.caculate_mousepos_from_realpos(event)
        self.mean_data.dx = (pos.x - self.mean_data.x)
        self.mean_data.dy = (pos.y - self.mean_data.y)
        self.show_meansure(event)     
    
    def show_meansure(self, event):
        # 删除之前的坐标信息
        self.canvas.delete("meansure")
        self.canvas.delete("coordinates")
        
        # 显示原始坐标信息        
        prg_pos = self.caculate_mousepos_from_realpos(Point(self.mean_data.canvas_x,self.mean_data.canvas_y))
        coords = f"({prg_pos.x:.0f},{prg_pos.y:.0f})"
        org_x = self.mean_data.canvas_x
        org_y = self.mean_data.canvas_y
        radius = 1
        self.canvas.create_oval(org_x - radius, org_y - radius, org_x + radius, org_y + radius, fill='darkviolet', outline='darkviolet', tags="meansure")
        self.canvas.create_text(org_x+50, org_y+12, text=coords, font=('Helvetica', 12, 'bold'), fill='darkviolet', tags="meansure")
        
        # 显示当前坐标信息和距离
        d_x = self.mean_data.dx
        d_y = self.mean_data.dy
        d_distance = math.sqrt(d_x*d_x+d_y*d_y)
        if d_x != 0 or d_y != 0:
            coords = f"d({d_x:.0f},{d_y:.0f},{d_distance:.2f})"
            radius = 1
            self.canvas.create_oval(event.x - radius, event.y - radius, event.x + radius, event.y + radius, fill='darkviolet', outline='darkviolet', tags="meansure")
            self.canvas.create_text(event.x+60, event.y+12, text=coords, font=('Helvetica', 12, 'bold'), fill='darkviolet', tags="meansure")

    def update_paras(self):
        self.paras = Paras(self.canvas, self.font_size, Config.canvas_pad_x + self.pad_x, Config.canvas_pad_y + self.pad_y, self.scale_factor)
    
    def caculate_font_size(self):
        self.font_size = int(self.default_font_size * self.scale_factor)

    
    def canvas_clear(self):        
        # 清空画布
        self.canvas.delete("all")
        # x轴
        #self.canvas.create_line(-5+Config.canvas_pad_x, 0+Config.canvas_pad_y, 20+Config.canvas_pad_x, 0+Config.canvas_pad_y, fill='green', width=3)
        # Y轴
        #self.canvas.create_line(0+Config.canvas_pad_x, -5+Config.canvas_pad_y, 0+Config.canvas_pad_x, 20+Config.canvas_pad_y, fill='blue', width=3)
    
    def package_reinit_bondingtable(self):
        self.package_init()
        self.package_preview()
        
    def package_init(self):   
        if self.cfg.check_valid() and self.plan.check_valid():
            try:
                platforms = self.plan.platforms()
                bondingtable = self.plan.bondingtables()
                self.package = Package(platforms, bondingtable)
            except Exception as e:
                raise Exception("package_init crashes : {e}")
            
    
    def package_preview(self):    
        
        self.canvas_clear()        
        # 绘制
        self.update_paras()
        self.package.preview(self.paras)
        self.update_scale_text()
        
    def zoom(self,scale_rate):
        #获取原始实际坐标
        #计算理论坐标
        zoom_pos0 = self.caculate_mousepos_from_realpos(self.mouse_real_pos)
        zoom_realpos0 = self.caculate_realpos_from_mousepos(zoom_pos0)
        self.scale_factor *= scale_rate
        #计算理论坐标在新scale_factor下的PADDING
        zoom_realpos1 = self.caculate_new_padding_after_new_scale_from_mousepos(zoom_pos0,scale_rate)
        self.pad_x = zoom_realpos1.x
        self.pad_y = zoom_realpos1.y
        self.package_preview()
    
    def zoom_out(self):   
        #下滚    
       self.zoom(0.9)
    
    def zoom_in(self):
        #上滚     
       self.zoom(1.1)
           
    def update_scale_text(self):
        self.scale_text.config(state='normal')
        self.scale_text.delete('1.0', tk.END)
        self.scale_text.insert(tk.END, f"Scale: {self.scale_factor:.2f}")
        self.scale_text.config(state='disabled')
    def save_canvas_as_png(self):
        # 获取当前屏幕截图
        x = self.canvas.winfo_rootx()
        y = self.canvas.winfo_rooty()
        x1 = x + self.canvas.winfo_width()
        y1 = y + self.canvas.winfo_height()
        image = ImageGrab.grab(bbox=(x, y, x1, y1))

        # 打开保存文件对话框
        filename = filedialog.asksaveasfilename(defaultextension=".png", filetypes=[("PNG files", "*.png")])
        if filename:
            # 保存图像为PNG格式
            image.save(filename, "PNG")
            
    def save_canvas_as_jpg(self):
        # 获取当前屏幕截图
        x = self.canvas.winfo_rootx()
        y = self.canvas.winfo_rooty()
        x1 = x + self.canvas.winfo_width()
        y1 = y + self.canvas.winfo_height()
        image = ImageGrab.grab(bbox=(x, y, x1, y1))

        # 打开保存文件对话框
        filename = filedialog.asksaveasfilename(defaultextension=".jpg", filetypes=[("JPEG files", "*.jpg")])
        if filename:
            # 保存图像时设置质量参数
            image.save(filename, "JPEG", quality=95)
                
    def view(self) :
        self.root.mainloop()


if __name__ == "__main__":    
    App().view()



